//
// Created by root on 2/22/18.
//

#include "oop.h"

#define MAX_SIZE    512
#define CATEGORY_CHAR   '='
#define PARAM_CHAR      '%'

int end_of_plain_text(int c) {
    return c != CATEGORY_CHAR && c != PARAM_CHAR;
}

static OopCodeBlock* alloc_block() {
    return (OopCodeBlock *)sys_malloc(sizeof(OopCodeBlock));
}

static OopCodeNode *init_text_node(String code) {
    OopCodeNode *node = (OopCodeNode *)sys_malloc(sizeof(OopCodeNode));
    node->loopBlockName = NULL;
    node->plainBlock = alloc_block();
    node->plainBlock->type = TYPE_TEXT;
    node->plainBlock->code = code;
    return node;
}

static OopCodeNode *init_param_node(String code) {
    OopCodeNode *node = (OopCodeNode *)sys_malloc(sizeof(OopCodeNode));
    node->loopBlockName = NULL;
    node->plainBlock = alloc_block();
    node->plainBlock->type = TYPE_PARAM;
    node->plainBlock->code = code;
    return node;
}

static OopCodeNode *init_loop_node(String code) {
    OopCodeNode *node = (OopCodeNode *)sys_malloc(sizeof(OopCodeNode));
    node->loopBlockName = code;
    node->loopBlock = init_queue();
    return node;
}

static OopCodeNode* init_node(String code, int repetition) {
    switch (repetition) {
        case 0:
            return init_text_node(code);
        case 1:
            return init_param_node(code);
        case 2:
            return init_loop_node(code);
        default:
            return NULL;
    }
}

static void parse_oop_loop(FILE* fp, OopCodeNode *loopNode) {
    OopCodeNode *node;
    String code = NULL;
    int loopEnd = FALSE;
    int count;
    int i;
    getc(fp);
    do {
        count = repetitions(fp, PARAM_CHAR);
        code = read_until(fp, end_of_plain_text);
        node = init_node(code, count);
        enqueue(loopNode->loopBlock, node);
        for (i = 0; i < count; i++) {
            getc(fp);
        }
        if (count == 2 && equals_string(code, END_OF_OOP_LOOP)) {
            loopEnd = TRUE;
        }
    } while (!feof(fp) && loopEnd == FALSE);
    getc(fp);
}

void* parse_oop_gen(FILE* fp) {
    String category = NULL;
    Map map = hashmap_new();
    Queue *queue;
    OopCodeNode *node;
    int c = 0;
    int count = 0;
    String code = NULL;
    int i;
    while (TRUE) {
        c = getc(fp);
        if (feof(fp)) break;

        if (c == CATEGORY_CHAR) {
            category = get_category(read_line(fp), CATEGORY_CHAR);
            queue = init_queue();

            while (!feof(fp) && fpeek(fp) != CATEGORY_CHAR){
                count = repetitions(fp, PARAM_CHAR);
                code = read_until(fp, end_of_plain_text);
                node = init_node(code, count);
                for (i = 0; i < count; i++) {
                    getc(fp);
                }
                if (count == 2) {
                    parse_oop_loop(fp, node);
                }
                enqueue(queue, node);
            }
            hashmap_put(map, category, queue);
        }
    }
    return map;
}

static String process_block(OopCodeBlock *block, Map params) {
    String code;
    int status;
    if (block->type == TYPE_TEXT) {
        return block->code;
    }
    else if (block->type == TYPE_PARAM) {
        status = hashmap_get(params, block->code, (void**)(&code));
        if (status == MAP_OK) {
            return code;
        }
        return "";
    }
    else {
        return NULL;
    }
}

static String gen_oop_loop_code(Queue *loop, Queue *properties) {  //queue map string
    String code = init_string();
    QueueNode *node = NULL;
    Map property;
    QueueNode *loopNode = NULL;
    OopCodeBlock *block;
    if ((!is_queue_empty(properties))) {
        node = properties->head;
        while (node != NULL) {
            property = (Map)(node->element);
            if ((!is_queue_empty(loop))) {
                loopNode = loop->head;
                while (loopNode != NULL) {
                    block = ((OopCodeNode *)(loopNode->element))->plainBlock;
                    code = concatenate(code, process_block(block, property));
                    loopNode = loopNode->next;
                }
            }
            node = node->next;
        }
    }
    return code;
}

String gen_oop_code(String category, Map params, Queue *properties, Queue *methods) {
    Queue *queue;
    String code = init_string();
    int status = hashmap_get(globalFunny->oopCodes, category, (void**)(&queue));
    if (status == MAP_OK) {
        QueueNode *node = NULL;
        if ((!is_queue_empty(queue))) {
            node = queue->head;
            while (node != NULL) {
                OopCodeNode *oopCodeNode = (OopCodeNode *)(node->element);
                if (oopCodeNode->loopBlockName == NULL) {
                    code = concatenate(code, process_block(oopCodeNode->plainBlock, params));
                } else if (equals_string(oopCodeNode->loopBlockName, OOP_LOOP_PROP)) {
                    code = concatenate(code, gen_oop_loop_code(oopCodeNode->loopBlock, properties));
                } else if (equals_string(oopCodeNode->loopBlockName, OOP_LOOP_METHOD)) {
                    code = concatenate(code, gen_oop_loop_code(oopCodeNode->loopBlock, methods));
                }
                node = node->next;
            }
        }
        return code;
    }
    return NULL;
}


void* convert_property(void* property) {
    Map map = hashmap_new();
    hashmap_put(map, "property", property);
    return map;
}

void* convert_method(void* method) {
    MethodMeta* methodMeta = (MethodMeta *)method;
    Map map = hashmap_new();
    hashmap_put(map, "methodName", methodMeta->methodName);
    hashmap_put(map, "methodArgs", join_from_queue(methodMeta->methodArgs, " "));
    hashmap_put(map, "methodDef", matcher(methodMeta->methodDef));
    return map;
}

static Queue* get_all_properties(TreeNode *classNode) {
    ClassMeta *classMeta = NULL;
    Queue *queue = init_queue();
    TreeNode *node = classNode;
    while (node != NULL) {
        classMeta = (ClassMeta *)node->data;
        append_queue(queue, classMeta->properties);
        node = node->parent;
    }
    return queue;
}

static Queue* get_all_methods(TreeNode *classNode) {
    ClassMeta *classMeta = NULL;
    Queue *queue = init_queue();
    TreeNode *node = classNode;
    while (node != NULL) {
        classMeta = (ClassMeta *)node->data;
        append_queue(queue, classMeta->methods);
        node = node->parent;
    }
    return queue;
}

static String get_method_name(MethodMeta *method) {
    return method->methodName;
}


String gen_defclass_code(TreeNode *classNode) {
    ClassMeta *currentClassMeta = (ClassMeta *)classNode->data;
    TreeNode *parentClassNode = classNode->parent;

    Queue *parentProperties = get_all_properties(parentClassNode);
    Queue *properties = init_queue();
    append_queue(properties, currentClassMeta->properties);
    append_queue(properties, parentProperties);

    Queue *methods = transform_queue(currentClassMeta->methods, convert_method);
    Queue *methodNames = transform_queue(get_all_methods(classNode), get_method_name);

    Map params = hashmap_new();
    hashmap_put(params, "className", classNode->name);
    hashmap_put(params, "properties", join_from_queue(transform_queue(properties, convert_property), " "));
    hashmap_put(params, "parentClassName", parentClassNode->name);
    hashmap_put(params, "parentProperties", join_from_queue(transform_queue(properties, parentProperties), " "));
    hashmap_put(params, "methods", join_from_queue(methodNames, " "));
    return gen_oop_code("DEFCLASS", params, properties, methods);
}


String gen_getprop_code(String object, String property) {
    Map params = hashmap_new();
    hashmap_put(params, "object", object);
    hashmap_put(params, "property", property);
    return gen_oop_code("GETPROP", params, NULL, NULL);
}


String gen_setprop_code(String object, String property, String value) {
    Map params = hashmap_new();
    hashmap_put(params, "object", object);
    hashmap_put(params, "property", property);
    hashmap_put(params, "value", value);
    return gen_oop_code("SETPROP", params, NULL, NULL);
}


String gen_sendmsg_code(String object, String message, String args) {
    Map params = hashmap_new();
    hashmap_put(params, "object", object);
    hashmap_put(params, "message", message);
    hashmap_put(params, "args", args);
    return gen_oop_code("SENDMSG", params, NULL, NULL);
}

static String init_value(String element, Map data) {
    String value;
    int status;
    status = hashmap_get(data, element, (void**)(&value));
    if (status == MAP_OK) {
        return value;
    } else {
        return "()";
    }
}

String gen_newinstance_code(TreeNode* classNode, Map assignments) {
    Queue *propertyValues = transform_queue_with_data(get_all_properties(classNode), assignments, init_value);
    Map params = hashmap_new();
    hashmap_put(params, "className", classNode->name);
    hashmap_put(params, "propertyValues", join_from_queue(propertyValues, " "));
    return gen_oop_code("NEWINSTANCE", params, NULL, NULL);

    return NULL;
}
